// Copyright (c) 2024 Huawei Device Co., Ltd.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "net/ssl/ssl_platform_key_ohos.h"

#include <memory>
#include <utility>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/strings/stringprintf.h"
#include "base/threading/scoped_blocking_call.h"
#include "net/cert/x509_certificate.h"
#include "net/ssl/ssl_platform_key_util.h"
#include "net/ssl/ssl_private_key.h"
#include "net/ssl/threaded_ssl_private_key.h"
#include "ohos/adapter/cert_manager/cert_manager_adapter.h"
#include "third_party/boringssl/src/include/openssl/bn.h"
#include "third_party/boringssl/src/include/openssl/bytestring.h"
#include "third_party/boringssl/src/include/openssl/ec.h"
#include "third_party/boringssl/src/include/openssl/ec_key.h"
#include "third_party/boringssl/src/include/openssl/ecdsa.h"
#include "third_party/boringssl/src/include/openssl/evp.h"
#include "third_party/boringssl/src/include/openssl/mem.h"
#include "third_party/boringssl/src/include/openssl/nid.h"
#include "third_party/boringssl/src/include/openssl/rsa.h"
#include "third_party/boringssl/src/include/openssl/ssl.h"
#include "third_party/boringssl/src/include/openssl/x509.h"

using ohos::adapter::CertManagerAdapter;

namespace net {

namespace {

class SSLPlatformKeyOHOS : public ThreadedSSLPrivateKey::Delegate {
 public:
  SSLPlatformKeyOHOS(int type,
                     std::string uri_cert)
      : type_(type) {
    uri_cert_ = uri_cert;
  }

  SSLPlatformKeyOHOS(const SSLPlatformKeyOHOS&) = delete;
  SSLPlatformKeyOHOS& operator=(const SSLPlatformKeyOHOS&) = delete;

  ~SSLPlatformKeyOHOS() override = default;

  std::string GetProviderName() override {
    // This logic accesses fields directly on the struct, so it may run on any
    // thread without caching.
    return base::StringPrintf("BoringSSL");
  }

  std::vector<uint16_t> GetAlgorithmPreferences() override {
    return SSLPrivateKey::DefaultAlgorithmPreferences(type_, true);
  }

  Error Sign(uint16_t algorithm,
             base::span<const uint8_t> input,
             std::vector<uint8_t>* signature) override {
    size_t size = 0;
    int ret = CertManagerAdapter::GetInstance().SignDataByUri(
        uri_cert_, (uint8_t*)input.data(), input.size(), nullptr, &size);
    if (ret) {
      return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED;
    }

    std::vector<uint8_t> out_data(size, 0);
    ret = CertManagerAdapter::GetInstance().SignDataByUri(
        uri_cert_, (uint8_t*)input.data(), input.size(), out_data.data(), &size);
    if (ret) {
      return ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED;
    }

    signature->assign(out_data.data(), out_data.data() + size);

    return OK;
  }

 private:
  int type_;
  std::string uri_cert_;
};

}  // namespace

bssl::UniquePtr<EVP_PKEY> FindPrivateKeyByCertOHOS(
    X509* cert,
    const std::vector<std::string>& pkey_files) {
  if (!cert || pkey_files.empty()) {
    return nullptr;
  }

  for (auto& pkey_file : pkey_files) {
    // load pkey file
    auto pkey_path = base::FilePath::FromUTF8Unsafe(pkey_file.c_str());
    std::string pkey_content;
    if (!base::ReadFileToString(pkey_path, &pkey_content)) {
      return nullptr;
    }

    // check file-format is pem or der
    if (pkey_content.find("-----BEGIN PRIVATE KEY-----") != std::string::npos) {
      // pem pkey file
      bssl::UniquePtr<BIO> pkey_bio(
          BIO_new_mem_buf(pkey_content.c_str(), pkey_content.size()));
      if (!pkey_bio) {
        return nullptr;
      }

      EVP_PKEY* pkey = nullptr;
      while ((pkey = PEM_read_bio_PrivateKey(pkey_bio.get(), nullptr, nullptr,
                                             nullptr))) {
        bssl::UniquePtr<EVP_PKEY> bssl_pkey(pkey);
        //returns 1 means the private key matches the certificate.
        if (X509_check_private_key(cert, pkey) == 1) {
          return bssl_pkey;
        }
      }
    } else {
      // der pkey file
      auto der_pkey = reinterpret_cast<const uint8_t*>(pkey_content.c_str());
      EVP_PKEY* pkey =
          d2i_AutoPrivateKey(nullptr, &der_pkey, pkey_content.size());
      if (pkey) {
        bssl::UniquePtr<EVP_PKEY> bssl_pkey(pkey);
        if (X509_check_private_key(cert, pkey) == 1) {
          return bssl_pkey;
        }
      }
    }
  }

  return nullptr;
}

scoped_refptr<SSLPrivateKey> FetchClientCertPrivateKey(
    const X509Certificate* certificate,
    X509* bssl_cert) {
  // This function may acquire the NSS lock or reenter this code via extension
  // hooks (such as smart card UI). To ensure threads are not starved or
  // deadlocked, the base::ScopedBlockingCall below increments the thread pool
  // capacity if this method takes too much time to run.
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::MAY_BLOCK);

  if (!bssl_cert) {
    return nullptr;
  }

  char* uri_cert = (char*)X509_get_ex_data(bssl_cert, 0);
  if (!uri_cert) {
    return nullptr;
  }
  std::string str_uri(uri_cert);
  free(uri_cert);

  int type;
  size_t max_length;
  if (!GetClientCertInfo(certificate, &type, &max_length)) {
    return nullptr;
  }

  return base::MakeRefCounted<ThreadedSSLPrivateKey>(
      std::make_unique<SSLPlatformKeyOHOS>(type, str_uri),
      GetSSLPlatformKeyTaskRunner());
}

}  // namespace net